import logging

import comm_id

log = logging.getLogger('peer')

#------------------------------------------------------------------------------
class PeerComm:
    """Encapsulates a transport method that a particular peer can
    use. Peers can have more than one of these, representing different
    open methods for communicating with the peer."""

    def __init__(self, peer, channel):
        self.peer_ = peer
        self.tx_ = peer.tx_
        self.channel_ = channel
        self.req_pending_ = False
        self.hs_req_pending_ = False

    def __repr__(self):
        return '<peer.PeerComm %s/%s>' % (self.peer_id(), self.type())

    def peer_id(self):
        """@return Id of the peer at the remote end of this peer comm"""
        return self.peer_.id_

    def iface_id(self):
        return self.channel_.iface_id()

    def channel_id(self):
        return self.channel_.channel_id()

    def type(self):
        """@return the type of channel connected to this peer"""
        return self.channel_.type()
    
    def comm_id(self):
        """type:iface_id:peer_id:channel_id"""
        return comm_id.CommId(self.type(), self.channel_.iface_id(),
                              self.channel_.channel_id(), self.peer_id())

    def match_comm_id(self, c_id):
        """@return True if this matches the given c_id, which may
        include ANY."""
        def m(c_id_val, val):
            return c_id_val == comm_id.CommId.ANY or str(c_id_val) == str(val)

        return (m(c_id.tx_type,    self.type()) and
                m(c_id.iface_id,   self.iface_id()) and
                m(c_id.channel_id, self.channel_id()) and
                m(c_id.peer_id,    self.peer_id()))
        
    def req_pending(self):
        """@return data request is pending"""
        return self.req_pending_

    def hs_req_pending(self):
        """@return hs request is pending"""
        return self.hs_req_pending_

    def signal_req_pending(self):
        """Signal to the client that there is a data req pending for
        this channel"""
        if self.req_pending_:
            return
        log.debug('%s: signal_req_pending' % self)
        self.req_pending_ = True
        self.tx_.api().send_data_req(self.comm_id())

    def signal_hs_req_pending(self):
        """Signal to the client that there is a hs req pending for
        this channel."""
        if self.hs_req_pending_:
            log.warn('%s: multiple calls to hs_req_pending' % self)
            return
        log.debug('%s: signal_hs_req_pending' % self)
        self.hs_req_pending_ = True
        self.tx_.api().send_handshake_req(self.comm_id())

    def deregister(self):
        """Remove this peer comm from the Peer and send a disconnected
        hint"""
        self.peer_.comms_.remove(self)
        self.tx_.api().send_disconnect_hint(self.comm_id())

    def send_handshake(self, hs):
        self.hs_req_pending_ = False
        self.channel_.send_handshake(self.tx_.neon_id(), hs)

    def send_data(self, data):
        self.req_pending_ = False
        self.channel_.send_data(self.tx_.neon_id(), data)
    
    def recvd_handshake(self, hs):
        log.debug('%s: recv_handshake "%s"' % (self, hs))
        self.tx_.api().send_handshake_recvd(self.comm_id(), hs)
        
    def recvd_data(self, data):
        log.debug('%s: recv_data "%s"' % (self, data))
        self.tx_.api().send_data_recvd(self.comm_id(), data)
        
#------------------------------------------------------------------------------
class Peer:
    """This object represents each active peer we can communicate
    with. There may be several PeerComm's per peer."""

    def __init__(self, id, tx):
        self.id_ = id
        self.tx_ = tx
        self.comms_ = []

    def __repr__(self):
        return '<peer.Peer %s %d comms>' % (self.id_, len(self.comms_))

    def id(self):
        """@return Id of the remote host represented by this Peer"""
        return self.id_

    def reg_comm(self, channel):
        pc = PeerComm(self, channel)
        self.add_comm(pc)
        return pc

    def add_comm(self, comm):
        assert not comm in self.comms_
        self.comms_.append(comm)

    def get_comm(self, channel):
        """@return the PeerComm for the appropriate channel"""
        for i in self.comms_:
            if i.channel_ == channel:
                return i
        return None

    def get_comm_by_id(self, c_id):
        """@return the matching PeerComm for the c_id spec."""
        for i in self.comms_:
            if i.match_comm_id(c_id):
                return i
        return None
    
    def resignal_reqs(self):
        """Resignal the missed data and handshake requests for the
        open channels"""
        for c in self.comms_:
            if c.hs_req_pending():
                self.tx_.api().send_handshake_req(c.comm_id())
        for c in self.comms_:
            if c.req_pending():
                self.tx_.api().send_data_req(c.comm_id())

    def send_handshake(self, c_id, hs):
        """Send a handshake for transport type to the peer"""
        c = self.get_comm_by_id(c_id)
        if c:
            c.send_handshake(hs)
        else:
            log.warn('send_handshake, non-existant transport %s' % c_id)

    def send_data(self, c_id, data):
        """Send data for transport type to the peer"""
        c = self.get_comm_by_id(c_id)
        if c:
            c.send_data(data)
        else:
            log.warn('send_data, non-existant transport %s' % c_id)
